﻿'    This file is part of The Global Distribution System.

'    The Global Distribution System is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 3 of the License, or
'    (at your option) any later version.

'    The Global Distribution System is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with The Global Distribution System.  If not, see <http://www.gnu.org/licenses/>.

Imports System.Runtime.Serialization
Imports DLL

Public Class BackendClass

    Private WithEvents TcpClient As New Net.Sockets.TcpClient
    Private InfiniteReaderThread As New System.Threading.Thread(AddressOf InfiniteReader) With {.IsBackground = True}

    Public Property IP As String = My.Settings.RemoteIP
    Public Property Port As Integer = 666I
    Public Property IsDisposed As Boolean = False
    Private WithEvents Timer As New System.Timers.Timer With {.AutoReset = True, .Interval = 60000I, .Enabled = False}


#Region " Events "

    Public Structure SearchResultObject
        Public IsSearch As Boolean
        Public Result As List(Of String)
    End Structure

    Public Event MessageFromServer(ByVal sender As Object, ByVal message As String)
    Public Event ConnectionEstablished(ByVal sender As Object)
    Public Event ConnectionFailed(ByVal sender As Object, ByVal ex As Exception)
    Public Event SearchResults(ByVal sender As Object, ByVal e As SearchResultObject)
    Public Event IncomingInformation(ByVal sender As Object, ByVal data As Information)

    Public Event DownloadingData(ByVal sender As Object, ByVal data As PokemonData)

    Public Event ChatMessage(ByVal sender As Object, ByVal e As ChatMessage)
    Public Event ChatIndividual(ByVal sender As Object, ByVal e As IndividualPerson)
    Public Event ChatFullList(ByVal sender As Object, ByVal e As FullList)

    ''' <summary>
    ''' Called whenever a message has been received from the server, passing the
    ''' message along as a single string.
    ''' </summary>
    ''' <param name="message">The message received from the server.</param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnMessageReceived(ByVal message As String)
        RaiseEvent MessageFromServer(Me, message)
    End Sub

    ''' <summary>
    ''' Called when Pokemon Data is received from the server to download.
    ''' </summary>
    ''' <param name="PD">The PokemonData</param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnDownloadDataReceived(ByVal PD As PokemonData)
        RaiseEvent DownloadingData(Me, PD)
    End Sub

    ''' <summary>
    ''' Called whenever a connection has been successfully established.
    ''' </summary>
    ''' <remarks></remarks>
    Protected Overridable Sub OnConnectionEstablished()
        InfiniteReaderThread.Start()
        Timer.Start()
        RaiseEvent ConnectionEstablished(Me)
    End Sub

    ''' <summary>
    ''' Called whenever a connection has failed/terminated/been aborted.
    ''' </summary>
    ''' <param name="ex">The exception data.</param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnConnectionFailed(ByVal ex As Exception)
        If Not Me.IsDisposed Then
            Timer.Stop()
            RaiseEvent ConnectionFailed(Me, ex)
            Me.Dispose()
        End If
    End Sub

    ''' <summary>
    ''' Called when search results come in from the server.
    ''' </summary>
    ''' <param name="search">Whether or not it was a specific search or a general search.</param>
    ''' <param name="results">The results in a List(Of String)</param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnSearchResultsReceived(ByVal search As Boolean, ByVal results As List(Of String))
        RaiseEvent SearchResults(Me, New SearchResultObject With {.IsSearch = search, .Result = results})
    End Sub

    ''' <summary>
    ''' Raised when special Pokemon information has been received.
    ''' </summary>
    ''' <param name="info">The information structure.</param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnPokemonInfoReceived(ByVal info As Information)
        RaiseEvent IncomingInformation(Me, info)
    End Sub

    ''' <summary>
    ''' This is called after a connection attempt has been completed and determines whether or not
    ''' the connection established successfully.
    ''' </summary>
    ''' <param name="IA"></param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnConnectionAttempt(ByVal IA As IAsyncResult)
        Select Case TcpClient.Connected
            Case True
                Me.OnConnectionEstablished()
            Case False
                Me.OnConnectionFailed(New Exception("The server is offline or unavailable!"))
        End Select
    End Sub

    ''' <summary>
    ''' Called when a Chat message comes in from the server.
    ''' </summary>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnChatMessageReceived(ByVal e As ChatMessage)
        RaiseEvent ChatMessage(Me, e)
    End Sub

    ''' <summary>
    ''' Raised on an individual chat login.
    ''' </summary>
    ''' <param name="e">The Individual Person Structure.</param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnChatIndividualDataReceived(ByVal e As IndividualPerson)
        RaiseEvent ChatIndividual(Me, e)
    End Sub

    ''' <summary>
    ''' Raised whenever a complete chat list is sent.
    ''' </summary>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Protected Overridable Sub OnChatFullListReceived(ByVal e As FullList)
        RaiseEvent ChatFullList(Me, e)
    End Sub

#End Region

#Region " Subs "

    ''' <summary>
    ''' Creates a new Backend Class.
    ''' </summary>
    ''' <param name="IP">The Remote IP Address.</param>
    ''' <param name="Port">The Remote Port.</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal IP As String, Optional ByVal Port As Integer = 666I)
        Me.IP = IP
        Me.Port = Port
        TcpClient.SendBufferSize = 1400
        TcpClient.ReceiveBufferSize = 1400
    End Sub

    ''' <summary>
    ''' Creates a new Backend Class.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
    End Sub

    ''' <summary>
    ''' Starts the Reader.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Begin()
        If Not IP = String.Empty AndAlso Port <> Nothing Then
            TcpClient.BeginConnect(IP, Port, New AsyncCallback(AddressOf OnConnectionAttempt), Nothing)
        End If
    End Sub

    ''' <summary>
    ''' Infinite Reading.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub InfiniteReader()
        Try
            Do
                Dim ReadSerializer As New Formatters.Binary.BinaryFormatter()
                Dim obj As Object = ReadSerializer.Deserialize(TcpClient.GetStream)
                Dim SpinOffThread As New System.Threading.Thread(New Threading.ParameterizedThreadStart(AddressOf GeneralHandling)) With {.IsBackground = True}
                SpinOffThread.Start(obj)
                'Call GeneralHandling(obj)
            Loop
        Catch ex As Exception
            Me.OnConnectionFailed(ex)
        End Try
    End Sub

    ''' <summary>
    ''' General Message Handler.
    ''' </summary>
    ''' <param name="obj">The Object to handle.</param>
    ''' <remarks></remarks>
    Private Sub GeneralHandling(ByVal obj As Object)

        'generic message
        If obj.GetType Is GetType(MessageStructure) Then
            Me.OnMessageReceived(CType(obj, MessageStructure).Message)
        End If

        'search result
        If obj.GetType Is GetType(SearchResults) Then
            Dim SR As SearchResults = CType(obj, SearchResults)
            System.Diagnostics.Debug.WriteLine(SR.Results)
            Me.OnSearchResultsReceived(SR.Search, SR.Results)
        End If

        'information
        If obj.GetType Is GetType(Information) Then
            Me.OnPokemonInfoReceived(CType(obj, Information))
        End If

        'chat message
        If obj.GetType Is GetType(ChatMessage) Then
            Me.OnChatMessageReceived(CType(obj, ChatMessage))
        End If

        'single user
        If obj.GetType Is GetType(IndividualPerson) Then
            Me.OnChatIndividualDataReceived(CType(obj, IndividualPerson))
        End If

        'full list
        If obj.GetType Is GetType(FullList) Then
            Me.OnChatFullListReceived(CType(obj, FullList))
        End If

        'Download data.
        If obj.GetType Is GetType(PokemonData) Then
            Me.OnDownloadDataReceived(CType(obj, PokemonData))
        End If

    End Sub

    ''' <summary>
    ''' Sends a Message to the server.
    ''' </summary>
    ''' <param name="obj">The object to send.</param>
    ''' <remarks></remarks>
    Public Sub SendMessage(ByVal obj As Object)
        Try
            SyncLock TcpClient.GetStream
                Dim Serializer As New Formatters.Binary.BinaryFormatter
                Serializer.Serialize(TcpClient.GetStream, obj)
            End SyncLock
        Catch ex As Exception
            Me.OnConnectionFailed(ex)
        End Try
    End Sub

    ''' <summary>
    ''' Calculates the MD5 Hash of a file.
    ''' </summary>
    ''' <param name="filepath"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function MD5CalcFile(ByVal filepath As String) As String

        ' open file (as read-only)
        Using reader As New System.IO.FileStream(filepath, IO.FileMode.Open, IO.FileAccess.Read)
            Using md5 As New System.Security.Cryptography.MD5CryptoServiceProvider

                ' hash contents of this stream
                Dim hash() As Byte = md5.ComputeHash(reader)

                ' return formatted hash
                Return ByteArrayToString(hash)

            End Using
        End Using

    End Function

    ' utility function to convert a byte array into a hex string
    Private Function ByteArrayToString(ByVal arrInput() As Byte) As String

        Dim sb As New System.Text.StringBuilder(arrInput.Length * 2)

        For i As Integer = 0 To arrInput.Length - 1
            sb.Append(arrInput(i).ToString("X2"))
        Next

        Return sb.ToString().ToLower

    End Function

    ''' <summary>
    ''' Disposes the Backend Class.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Dispose()
        If Not Me.IsDisposed Then
            Me.IsDisposed = True
            Try
                InfiniteReaderThread.Abort()
            Catch
            End Try
            TcpClient.Close()
            Timer.Dispose()
        End If

    End Sub

#End Region

    Private Sub Timer_Elapsed(ByVal sender As Object, ByVal e As System.Timers.ElapsedEventArgs) Handles Timer.Elapsed
        Me.SendMessage(New MessageStructure With {.Message = "0"})
    End Sub

End Class